﻿namespace Microsoft.Tools.WixDeployment.Extensions
{
    using System;
    using System.Reflection;
    using System.Xml;
    using System.Xml.Schema;
    using Microsoft.Tools.WindowsInstallerXml;
    using System.Collections.Generic;
    using System.Globalization;

    /// <summary>
    /// The compiler for the Windows Installer XML Toolset Biztalk Extension.
    /// </summary>
    public class WixDeploymentCompiler : CompilerExtension
    {
        private XmlSchema schema;
        private ElementNodeParser nodeParser;
        private static int SqlCmdElementCount = 1;
        /// <summary>
        /// Creates a new WixDeploymentCompiler.
        /// </summary>
        public WixDeploymentCompiler()
        {            
            this.schema = LoadXmlSchemaHelper(Assembly.GetExecutingAssembly(), "Microsoft.Tools.WixDeployment.Extensions.Xsd.wixdeploy.xsd");
            Dictionary<string, ParseElementNode> factory = new Dictionary<string, ParseElementNode>();
            Dictionary<string, ParseElementNode> rootFactory = new Dictionary<string, ParseElementNode>();
            factory.Add("\\BiztalkServerApplications", BiztalkServerApplicationsElement);
            factory.Add("\\BiztalkServerApplications\\BiztalkServerApplication", BiztalkServerApplicationElement);
            factory.Add("\\BiztalkServerApplications\\BiztalkServerApplication\\BiztalkResource", BiztalkResourceElement);
            factory.Add("\\BiztalkServerApplications\\BiztalkServerApplication\\BiztalkReferences", BiztalkReferencesElement);

            factory.Add("\\BamActivities", BamActivitesElement);
            factory.Add("\\BamActivities\\Bam", BamElement);

            factory.Add("\\VsDbCmd10", VsDbCmd10Element);
            factory.Add("\\VsDbCmd10\\SqlCmdVariable", VsDbCmd10SqlCmdVariableElement);
            
            factory.Add("\\SqlCmdQuery", SqlCmdElement);
            factory.Add("\\SqlCmdQuery\\File", SqlCmdFileElement);
            factory.Add("\\SqlCmdQuery\\Query", SqlCmdQueryElement);
            factory.Add("\\SqlCmdQuery\\SqlCmdVariable", SqlCmdVariablesElement);
            
            nodeParser = new ElementNodeParser(this, factory, rootFactory);
        }

        /// <summary>
        /// Gets the schema for this extension.
        /// </summary>
        /// <value>Schema for this extension.</value>
        public override XmlSchema Schema
        {
            get { return this.schema; }
        }

        /// <summary>
        /// Processes an element for the Compiler.
        /// </summary>
        /// <param name="sourceLineNumbers">Source line number for the parent element.</param>
        /// <param name="parentElement">Parent element of element to process.</param>
        /// <param name="element">Element to process.</param>
        /// <param name="contextValues">Extra information about the context in which this element is being parsed.</param>
        public override void ParseElement(SourceLineNumberCollection sourceLineNumbers, XmlElement parentElement, XmlElement element, params string[] contextValues)
        {
            //System.Diagnostics.Debugger.Launch();
            if (parentElement == null) return;
            if (element == null) return;
            this.Core.EnsureTable(sourceLineNumbers, Constants.BiztalkApplicationsTable);
            this.Core.EnsureTable(sourceLineNumbers, Constants.BiztalkApplicationResourcesTable);
            this.Core.EnsureTable(sourceLineNumbers, Constants.BiztalkApplicationReferencesTable);
            this.Core.EnsureTable(sourceLineNumbers, Constants.BamActivitiesTable);
            this.Core.EnsureTable(sourceLineNumbers, Constants.VsdbCmd10Table);
            this.Core.EnsureTable(sourceLineNumbers, Constants.VsdbCmd10SqlCmdVariablesTable);
            this.Core.EnsureTable(sourceLineNumbers, Constants.SqlCmdQueryTable);
            this.Core.EnsureTable(sourceLineNumbers, Constants.SqlCmdVariablesTable);
            
            Dictionary<string, string> result = new Dictionary<string, string>();
            if (parentElement.LocalName != "Component") this.Core.UnexpectedElement(parentElement, element);

            if (contextValues != null)
            {
                string componentId = contextValues[0];
                string directoryId = contextValues[1];
                result.Add(Constants.ComponentId, componentId);
                result.Add(Constants.DirectoryId, directoryId);
                result.Add(Constants.ParentElementPath, "");
            }

            ParseElementNode parseElementNode = null;
            if (!nodeParser.TryGetValue(element.LocalName, out parseElementNode))
            {
                if (element.NamespaceURI == this.Schema.TargetNamespace)
                    this.Core.UnexpectedElement(parentElement, element);
            }
            else
            {
                parseElementNode(element, result);
            }
        }

        private void BiztalkServerApplicationsElement(XmlNode node, Dictionary<string, string> parentResult)
        {
            AttributeParser parser = new AttributeParser(this, new string[] 
            { 
                Constants.BiztalkManagementDatabaseName, 
                Constants.BiztalkManagementInstanceName 
            });

            Dictionary<string, string> result = parser.ParseAttributes(node, ValidateAttribute);
            result.Add(Constants.ComponentId, parentResult[Constants.ComponentId]);
            result.Add(Constants.DirectoryId, parentResult[Constants.DirectoryId]);
            result.Add(Constants.ParentElementPath, parentResult[Constants.ParentElementPath] + "\\" + node.LocalName);
            nodeParser.Parse(node, result, "BiztalkServerApplication");
        }

        private void BiztalkServerApplicationElement(XmlNode node, Dictionary<string, string> parentParsedData)
        {
            AttributeParser parser = new AttributeParser(this, new string[] { 
                Constants.Id,
                Constants.ApplicationName
            });
            
            Dictionary<string, string> result = parser.ParseAttributes(node, ValidateAttribute);
            result.Add(Constants.ParentElementPath, parentParsedData[Constants.ParentElementPath] + "\\" + node.LocalName);
            if (!this.Core.EncounteredError)
            {
                SourceLineNumberCollection sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
                Row row = this.Core.CreateRow(sourceLineNumbers, Constants.BiztalkApplicationsTable);                
                row[0] = result[Constants.Id];
                row[1] = parentParsedData[Constants.ComponentId];
                row[2] = result[Constants.ApplicationName];
                row[3] = parentParsedData[Constants.BiztalkManagementInstanceName];
                row[4] = parentParsedData[Constants.BiztalkManagementDatabaseName];
                row[5] = "[" + parentParsedData[Constants.DirectoryId] + "]";

                this.Core.CreateWixSimpleReferenceRow(sourceLineNumbers, "CustomAction", "CreateBiztalkApplicationExecute");
            }
            nodeParser.Parse(node, result, "BiztalkResource", "BiztalkReferences");            
        }

        private void BiztalkReferencesElement(XmlNode node, Dictionary<string, string> parent)
        {
            AttributeParser parser = new AttributeParser(this, new string[] {
                Constants.ApplicationName
            });

            Dictionary<string, string> result = parser.ParseAttributes(node, ValidateAttribute);
            if (!this.Core.EncounteredError)
            {
                SourceLineNumberCollection sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
                Row row = this.Core.CreateRow(sourceLineNumbers, Constants.BiztalkApplicationReferencesTable);
                row[0] = parent[Constants.Id];
                row[1] = result[Constants.ApplicationName];
            }
        }

        private void BiztalkResourceElement(XmlNode node, Dictionary<string, string> parent)
        {
            AttributeParser parser = new AttributeParser(this, new string[] {
                Constants.Id,
                Constants.FilePath,
                Constants.Type
            });

            Dictionary<string, string> result = parser.ParseAttributes(node, ValidateAttribute);
            if (!this.Core.EncounteredError)
            {
                SourceLineNumberCollection sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
                Row row = this.Core.CreateRow(sourceLineNumbers, Constants.BiztalkApplicationResourcesTable);
                row[0] = result[Constants.Id];
                row[1] = parent[Constants.Id];
                row[2] = result[Constants.FilePath];
                row[3] = result[Constants.Type];
            }
            
        }

        private void BamActivitesElement(XmlNode node, Dictionary<string, string> parent)
        {
            AttributeParser parser = new AttributeParser(this, new string[] {
                Constants.BamInstanceName,
                Constants.BamDatabaseName,
                //Constants.BamArchiveInstanceName,
                //Constants.BamArchiveDatabaseName,
                Constants.ActivityWindow,
                Constants.ViewAccessAccounts
            });

            Dictionary<string, string> result = parser.ParseAttributes(node, ValidateAttribute);
            result.Add(Constants.ComponentId, parent[Constants.ComponentId]);
            result.Add(Constants.ParentElementPath, parent[Constants.ParentElementPath] + "\\" + node.LocalName);
            nodeParser.Parse(node, result, "Bam");
        }

        private void BamElement(XmlNode node, Dictionary<string, string> parent)
        {
            AttributeParser parser = new AttributeParser(this, new string[] {
                Constants.Id,
                Constants.Source,
                Constants.DropOnUninstall
            });

            Dictionary<string, string> result = parser.ParseAttributes(node, ValidateAttribute);

            if (!this.Core.EncounteredError)
            {
                SourceLineNumberCollection sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
                Row row = this.Core.CreateRow(sourceLineNumbers, Constants.BamActivitiesTable);
                row[0] = result[Constants.Id];
                row[1] = parent[Constants.ComponentId];
                row[2] = result[Constants.Source];
                row[3] = result[Constants.DropOnUninstall];
                row[4] = parent[Constants.BamInstanceName];
                row[5] = parent[Constants.BamDatabaseName];
                //row[6] = parent[Constants.BamArchiveInstanceName];
                //row[7] = parent[Constants.BamArchiveDatabaseName];
                row[6] = parent[Constants.ActivityWindow];
                row[7] = parent[Constants.ViewAccessAccounts];

            }
        }

        private void VsDbCmd10Element(XmlNode node, Dictionary<string, string> parentParsedData)
        {
            AttributeParser parser = new AttributeParser(this, new string[] { 
                Constants.Id,
                Constants.ConnectionString,
                Constants.DatabaseName,
                Constants.DatabaseSchemaProvider,
                Constants.DeploymentConfigurationFile,
                Constants.ModelFile,
                Constants.SqlCmdVarsFile,
                Constants.PostDeploymentScript,
                Constants.PreDeploymentScript
            });

            Dictionary<string, string> result = parser.ParseAttributes(node, ValidateAttribute);
            result.Add(Constants.ParentElementPath, parentParsedData[Constants.ParentElementPath] + "\\" + node.LocalName);
            if (!this.Core.EncounteredError)
            {
                SourceLineNumberCollection sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
                Row row = this.Core.CreateRow(sourceLineNumbers, Constants.VsdbCmd10Table);
                row[0] = result[Constants.Id];
                row[1] = parentParsedData[Constants.ComponentId];
                row[2] = result[Constants.ConnectionString];
                row[3] = result[Constants.DatabaseName];
                row[4] = result[Constants.DatabaseSchemaProvider];
                row[5] = result[Constants.DeploymentConfigurationFile];
                row[6] = result[Constants.ModelFile];
                row[7] = result[Constants.SqlCmdVarsFile];
                row[8] = result[Constants.PostDeploymentScript];
                row[9] = result[Constants.PreDeploymentScript];
                
                this.Core.CreateWixSimpleReferenceRow(sourceLineNumbers, "CustomAction", "CreateDatabaseExecute");
            }
            nodeParser.Parse(node, result, "SqlCmdVariable");
        }

        private void VsDbCmd10SqlCmdVariableElement(XmlNode node, Dictionary<string, string> parent)
        {
            Dictionary<string, string> result = ParseSqlCmdVariableElement(node);
            if (!this.Core.EncounteredError)
            {
                SourceLineNumberCollection sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
                Row row = this.Core.CreateRow(sourceLineNumbers, Constants.VsdbCmd10SqlCmdVariablesTable);
                row[0] = parent[Constants.Id];
                row[1] = result[Constants.SqlCmdVariableName];
                row[2] = result[Constants.SqlCmdVariableValue];
            }
        }

        private void SqlCmdElement(XmlNode node, Dictionary<string, string> parentParsedData)
        {
            AttributeParser parser = new AttributeParser(this, new string[] { 
                Constants.Id,
                Constants.ConnectionString}, new string[] {
                Constants.Sequence
            });

            Dictionary<string, string> result = parser.ParseAttributes(node, ValidateAttribute);
            result.Add(Constants.ComponentId, parentParsedData[Constants.ComponentId]);           
            result.Add(Constants.ParentElementPath, parentParsedData[Constants.ParentElementPath] + "\\" + node.LocalName);
            
            nodeParser.Parse(node, result, "File", "Query");
        }

        private void SqlCmdFileElement(XmlNode node, Dictionary<string, string> parentParsedData)
        {
            AttributeParser parser = new AttributeParser(this, new string[] { 
                Constants.Source,
                Constants.Sequence
            });

            Dictionary<string, string> result = parser.ParseAttributes(node, ValidateAttribute);
            
            if (!this.Core.EncounteredError)
            {
                SourceLineNumberCollection sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
                Row row = this.Core.CreateRow(sourceLineNumbers, Constants.SqlCmdQueryTable);
                row[0] = String.Format(CultureInfo.InvariantCulture, "s{0}", SqlCmdElementCount++);
                row[1] = parentParsedData[Constants.ComponentId];
                row[2] = parentParsedData[Constants.ConnectionString];
                row[3] = 1; //1 for File and 2 for Query
                row[4] = result[Constants.Source];
                row[5] = Int16.Parse(parentParsedData[Constants.Sequence] ?? "0") * 4 + Int16.Parse(result[Constants.Sequence]);
                row[6] = parentParsedData[Constants.Id];
                row[7] = node.InnerText ?? String.Empty;
                this.Core.CreateWixSimpleReferenceRow(sourceLineNumbers, "CustomAction", "CreateSqlCmdExecute");
            }
        }

        private void SqlCmdQueryElement(XmlNode node, Dictionary<string, string> parentParsedData)
        {
            AttributeParser parser = new AttributeParser(this, new string[] { 
                Constants.QueryString,
                Constants.Sequence
            });

            Dictionary<string, string> result = parser.ParseAttributes(node, ValidateAttribute);

            if (!this.Core.EncounteredError)
            {
                SourceLineNumberCollection sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
                Row row = this.Core.CreateRow(sourceLineNumbers, Constants.SqlCmdQueryTable);
                row[0] = String.Format(CultureInfo.InvariantCulture, "s{0}", SqlCmdElementCount++);
                row[1] = parentParsedData[Constants.ComponentId];
                row[2] = parentParsedData[Constants.ConnectionString];
                row[3] = 2; //1 for File and 2 for Query
                row[4] = result[Constants.QueryString];
                row[5] = Int16.Parse(parentParsedData[Constants.Sequence] ?? "0") * 4 + Int16.Parse(result[Constants.Sequence]);
                row[6] = parentParsedData[Constants.Id];
                row[7] = node.InnerText ?? String.Empty;
                this.Core.CreateWixSimpleReferenceRow(sourceLineNumbers, "CustomAction", "CreateSqlCmdExecute");
            }
        }

        private void SqlCmdVariablesElement(XmlNode node, Dictionary<string, string> parentParsedData)
        {
            Dictionary<string, string> result = ParseSqlCmdVariableElement(node);
            if (!this.Core.EncounteredError)
            {
                SourceLineNumberCollection sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
                Row row = this.Core.CreateRow(sourceLineNumbers, Constants.SqlCmdVariablesTable);
                row[0] = parentParsedData[Constants.Id];
                row[1] = result[Constants.SqlCmdVariableName];
                row[2] = result[Constants.SqlCmdVariableValue];
            }
        }

        private Dictionary<string, string> ParseSqlCmdVariableElement(XmlNode node)
        {
            AttributeParser parser = new AttributeParser(this, new string[] {
                Constants.SqlCmdVariableName,
                Constants.SqlCmdVariableValue
            });
            return parser.ParseAttributes(node, ValidateAttribute);
            
        }

        private bool ValidateAttribute(XmlAttribute attribute)
        {
            return 0 == attribute.NamespaceURI.Length || attribute.NamespaceURI == this.schema.TargetNamespace;
        }
    }
}
